home *** CD-ROM | disk | FTP | other *** search
Korn shell script | 1997-08-26 | 19.1 KB | 677 lines |
- #!/bin/ksh
- # @(#) fbackup.ksh 4.5 97/01/06
- # Make backup list before feeding it to cpio to help tape drive stream.
- # XENIX 2.3.2 tape driver hangs if written with much more than 64K at a time.
- # cpio allocates 2-10 times the given buffer size (as much as it can,
- # up to 10 times).
- # 91/05/04 john h. dubois iii (john@armory.com)
- # 91/06/11 change find cmd to generate relative pathnames
- # 91/06/14 changed buffer size to 65536
- # 91/10/24 added 'a' option to cpio so we can tell how long it's been since
- # someone used a file, and V to indicate progression of backup
- # 91/12/11 added -K 150000 to cpio options
- # 92/03/21 Use -mount option; back up selected fs
- # 92/07/01 Allow tapesize to be given on cmd line
- # 92/07/10 Don't backup #files or corefiles
- # 94/10/03 Make tape size of 0 mean no limit. Added s and l options.
- # 94/12/04 Added fhnorx options. Modified to do multi-filesystem backup.
- # 94/12/09 Added t option.
- # 94/12/14 Fixed BLOCKSIZE use.
- # 94/12/30 Allow a prepared filelist to be given for each filesystem.
- # 95/02/12 Added w option.
- # 95/03/06 Renamed to avoid confusion with /usr/lib/sysadmin/cbackup.
- # 95/05/27 Give parameters to slow in the correct units.
- # 95/08/31 Added c option. Not tested yet.
- # 96/01/20 Make tmpfile more carefully.
- # 96/02/06 Added generation of control device name
- # 96/02/13 Added p option.
- # 96/03/27 Make TAPESIZE 0 by default.
- # 96/04/09 Try both rct0 and rStp0
- # 96/04/15 Get defaults from /etc/default/fbackup. Check remote backups too.
- # Added S option.
- # 96/06/19 Added C option.
- # 96/06/21 Write status to logfile. Added L option. Print tape amount info.
- # 96/06/23 Let -S be given without -t. Rewind before backup. Added N option.
- # 96/06/28 Use coprocess for logger. Added g option.
- # 96/07/12 Make coprocess output to stdout properly.
- # 97/01/06 Added dD options.
-
- set -A Args -- "$@" # Save args because set -A loses them in some versions
- set -A rDevs /dev/r{ct,Stp}0
- set -A nrDevs /dev/nr{ct,Stp}0
- set -- "${Args[@]}" # Restore args
- ControlDev=
- deftape=0
- DEVICE=
- lname=${0##*/}
- filelist=/tmp/#bk.$$.{}
- PrepList=false
- debug=false
- rVerify=false
- TestTape=false
- CheckOnly=false
- logfile=
- # Typical kernel tape buffer size size, which is the largest size the DAT
- # driver will accept.
- typeset -i DefBlockSize=64 NumGood=0 BLOCKSIZE TAPESIZE=-1 WRITERATE=0
- REMOTE=
- CHECK=
- PRESERVELISTS=
- NOLOG=
- NOREWIND=
- CPIOPATH=cpio
- defaultFile=/etc/default/fbackup
- Usage=\
- "Usage: $lname [-cChNptS] [-f<find-options>] [-r<remote-system[:remote-user]>]
- [-o<device>] [-l<filename>] [-s<tapesize>] [-g<logfile-name>]
- [-b<blocksize>] [-w<write-rate>] [-[dD]<cpio-path>] <fs-root> ..."
-
-
- [ -f $defaultFile -a -r $defaultFile ] && . $defaultFile
-
- while getopts cChpl:Ls:o:f:nNxr:tb:w:Sg:d:D: opt "$@"; do
- case $opt in
- h)
- print -r -- \
- "$lname: Make cpio backups of filesystems on tape.
- $Usage
- If more than one filesystem-root is given, they are stored in seperate cpio
- archives on the tape, with filemarks between them. This is achieved by using
- the no-rewind tape device (${nrDevs[0]}, or if it does not exist, ${nrDevs[1]})
- and closing and opening the device between extents. All filesystems must be
- mounted to be backed up. find is always given the -mount option, so only files
- on the same filesystems as the <fs-root> arguments are backed up.
- For each archive, a status line giving the amount of data written to tape is
- printed. The same information is written to a logfile named /tmp/#fbackup.pid,
- where pid is the process ID of the program.
- Options:
- The defaults for some of the following options can be set by assigning values
- to variables in the file $defaultFile, in the form
- VARNAME=value
- For options that do not take a value, use
- VARNAME=1
- The variable names are given in parentheses after the option descriptions.
- -h: Print this help.
- -c: Check backups after they are written. This does not work with multivolume
- tape backups, or backups appended to a tape. The same blocksize used to
- write the tape is used to read it. It is read at full speed regardless of
- what the write-rate is set to. (CHECK)
- -C: Check an already-written backup. The number of filesystem-root names given
- on the backup determines how many tape extents are checked. However, the
- filesystem-roots need not exist.
- -f<find-options>: Pass the given options to find, to restrict the files
- archived. If more than one word is used, they should be quoted. (FINDOPTS)
- -N: Do not rewind the tape before writing to it.
- -o<device>: Write output to device <device>, instead of the default device
- (${rDevs[0]} or ${rDevs[1]} if a single filesystem is being backed up, or
- ${nrDevs[0]} or ${nrDevs[1]} if multiple filesystems are being backed up).
- If multiple filesystems are to be backed up, be sure to give a device which
- does not rewind when closed. Use a device name of '-' to write to the
- standard output. The name of the control device will be determined by
- replacing everything up through a leading 'r' in the trailing component of
- the device name with 'x'; e.g., the control devices for /dev/nurStp0 and
- /dev/rct0 would be /dev/xStp0 and /dev/xct0 respectively. If the control
- device does not exist, the read/write device is used instead. (DEVICE)
- -p: Preserve file lists (in /tmp/#bk.pid.fs-name). (PRESERVELISTS)
- -s<tapesize>: Set the size of the tape media to <tapesize> KB. The default is
- 0, which causes $lname to write to the end of the media. If multiple
- filesystems are given or backup is being done to a drive on a remote
- system, a tapesize of 0 is always used, since only individual cpio sessions
- keep track of tape size and the rcmd pipe cannot be reopened. (TAPESIZE)
- -b<blocksize>: Set the size of blocks written to the tape device (this does not
- affect the size of blocks as they appear on tape). <blocksize> is given in
- kilobytes. The default is $DefBlockSize. (BLOCKSIZE)
- -l<filename>: Take a list of files to be backed up from <filename> instead of
- generating a list. If the filename is relative (does not start with /),
- it is taken to be relative to the filesystem mount point. If the name
- includes the string '{}', the {} is replaced by the trailing part of the
- mount point path, except that if the mount point is /, it is replaced with
- 'root'. The default filename is is /tmp/#bk.pid.{}, where pid is the
- process ID of the backup program, so that the filelist for e.g. /u might be
- stored in /tmp/#bk.12045.u. Unless -l is given, any prexisting file is
- overwritten.
- -L: Do not create or write to the logfile. (NOLOG)
- -g<logfile-name>: Write to <logfile-name> instead of the default log file.
- -n: Use the no-rewind tape device (${nrDevs[0]} or ${nrDevs[1]}). (NRDEVICE)
- -r<remote-system[:remote-user]>: Use rcmd to back up onto a tape drive on a
- remote system. If a : and a user name are appended to the system name,
- $lname attempt to execute the dd command that is used to write to tape as
- that user on the remote system; if not, it attempts to execute the command
- as the same user as the one running $lname. Whatever user the remote
- command is run as, that user must have user equivalency for the same user
- on the local system. (REMOTE)
- -t: Used only with -r. Instead of being written to a tape device, the cpio
- stream is piped through cpio -it on the remote system (without writing
- anything to disk) to test the archive integrity after it has crossed
- the network.
- -S: Check status and accessibility of local or remote tape control device with
- 'tape status'.
- -d<cpio-path>: Use <cpio-path> as the cpio command. The default is to use
- \"cpio\" wherever it exists in the command search path. (CPIOPATH)
- -D<cpio-path>: Use <cpio-path> as the cpio used to test archive integrity.
- This path is used on the remote system if -t is given, or -c or -C is used
- with -r. (TCPIOPATH)
- -w<write-rate>: Pipe the data stream through 'slow', which throttles the data
- rate to <write-rate> kilobytes per second. This can be used to reduce the
- backup load on a system, or (if used with -r) to reduce the network load.
- If used with -t, the 'slow' command will be run on the remote system
- instead of the local system, with its output sent to cpio. This can be
- used to exercise (test) network buffers more thoroughly than -t by itself.
- Use of -w requires the 'slow' command to be available on the local or, if
- used with -t, the remote system. (WRITERATE)"
- exit 0
- ;;
- g)
- logfile=$OPTARG
- ;;
- C)
- CheckOnly=true
- CHECK=1
- ;;
- c)
- CHECK=1
- ;;
- l)
- filelist=$OPTARG
- PrepList=true
- ;;
- L)
- NOLOG=1;;
- p)
- PRESERVELISTS=1
- ;;
- f)
- FINDOPTS=$OPTARG
- ;;
- n)
- NRDEVICE=1
- ;;
- b)
- BLOCKSIZE=$OPTARG
- # Must test exit status in a separate operation
- [ $? -eq 0 ] || exit 1
- ;;
- s)
- TAPESIZE=$OPTARG
- [ $? -eq 0 ] || exit 1
- ;;
- w)
- WRITERATE=$OPTARG
- [ $? -eq 0 ] || exit 1
- ;;
- o)
- DEVICE=$OPTARG
- ;;
- x)
- debug=true
- ;;
- r)
- REMOTE=$OPTARG
- ;;
- t)
- rVerify=true
- ;;
- N)
- NOREWIND=1
- ;;
- S)
- TestTape=true
- ;;
- d)
- CPIOPATH=$OPTARG
- ;;
- D)
- TCPIOPATH=$OPTARG
- ;;
- ?)
- print -ru2 "Use -h for help."
- exit 1
- ;;
- esac
- done
-
- [ -z "$TCPIOPATH" ] && TCPIOPATH=$CPIOPATH
-
- if [ -n "$REMOTE" ]; then
- RemoteSys=${REMOTE%%:*}
- RemoteUser=${REMOTE#*:}
- else
- if $rVerify; then
- print -ru2 "Must give system name (-r or REMOTE) with -t."
- exit 1
- fi
- fi
-
- if [ -n "$NRDEVICE" ]; then
- if [ -n "$DEVICE" ]; then
- print -ru2 "Cannot give both -o (DEVICE) and -n (NRDEVICE) options."
- exit 1
- fi
- nDev=n
- fi
-
- [ BLOCKSIZE -eq 0 ] && BLOCKSIZE=DefBlockSize
- [ WRITERATE -gt 0 ] &&
- SlowCmd="slow -s ${BLOCKSIZE}k -b ${BLOCKSIZE}k ${WRITERATE}k"
-
- # remove args that were options
- let OPTIND=OPTIND-1
- shift $OPTIND
-
- case $# in
- 0)
- if ! $TestTape; then
- print -ru2 -- "$Usage
- Use -h for help."
- exit
- fi
- ;;
- 1)
- ;;
- *)
- if [ TAPESIZE -gt 0 ]; then
- print -ru2 -- \
- "$lname: cannot give a tape size for multi-filesystem backup."
- exit 1
- fi
- TAPESIZE=0
- ;;
- esac
-
- if [ -n "$REMOTE" ]; then
- if [ TAPESIZE -gt 0 ]; then
- print -ru2 -- "$lname: cannot give a tape size for remote backup."
- exit 1
- fi
- TAPESIZE=0
- if [ "$DEVICE" = - ]; then
- print -ru2 -- "$lname: cannot write to stdout for remote backup."
- exit 1
- fi
- if [ -n "$RemoteUser" ]; then
- UserArg="-l $RemoteUser"
- else
- UserArg=
- fi
- fi
-
- # Select devices to try
- if [ -z "$DEVICE" ]; then
- [ $# -eq 1 ] && set -A Devs "${rDevs[@]}" || set -A Devs "${nrDevs[@]}"
- else
- set -A Devs -- "$DEVICE" # make this the only device in list
- fi
-
- if [ -z "$REMOTE" -a "$DEVICE" != - ]; then
- for DEVICE in "${Devs[@]}"; do
- [ -c "$DEVICE" ] && break
- done
- if [ ! -c "$DEVICE" ]; then
- print -ru2 -- "No character device in: ${Devs[*]}. Exiting."
- exit 1
- fi
- fi
-
- # Check these before we begin backing up any filesystems
- if ! $CheckOnly; then
- for dir; do
- if [ ! -d "$dir" ]; then
- print -ru2 -- "$dir is not a directory. Exiting."
- exit 1
- fi
- if [ ! -x "$dir" ]; then
- print -ru2 -- "Cannot execute directory $dir. Exiting."
- exit 1
- fi
- done
- fi
-
- [ TAPESIZE -lt 0 ] && TAPESIZE=$deftape
- [ TAPESIZE -eq 0 ] && TapeSizeOpt= || TapeSizeOpt="-K $TAPESIZE"
-
- if [ -n "$REMOTE" -o "$DEVICE" = - ]; then
- DevArg=
- else
- DevArg="-O $DEVICE"
- fi
-
- if $debug; then
- print -ru2 \
- "Tape Size: $TAPESIZE
- Block Size: $BLOCKSIZE
- Write Rate: $WRITERATE
- filesystems: $*
- device: $DEVICE
- find options: $FINDOPTS
- filelist: $filelist
- remote system: $RemoteSys
- remote user: $RemoteUser
- Check: $CHECK
- Nolog: $NOLOG
- Preserve: $PRESERVELISTS
- Nrdevice: $NRDEVICE"
- set -x
- fi
-
- # @(#) mktempfile 1.0 96/06/22
- # 96/01/20 jhdiii
-
- # Usage: mkfiles [-n] name ...
- # Creates the named files with some attempt at security.
- # This will be more reliable if user do not have chown authority.
- # Any file that contains no / characters is created in the user's tempdir.
- # If TMP was not set, it is set by this function.
- # The resulting filenames are put in mkfiles_ret[0..n-1]
- # Returns 0 on success; prints a diagnostic message & returns 1 on failure.
- # Options:
- # If -n is given, files are not put in the tempdir, and TMP is not set.
- # Use -- to force termination of the option list.
- function mkfiles {
- typeset file dotmp=true
- typeset -i i=0
-
- if [ "$1" = -n ]; then
- dotmp=false
- shift
- else
- : ${TMP:=$TMPDIR}
- : ${TMP:=/tmp}
- fi
- if [ "$1" = -- ]; then
- shift
- fi
- for file; do
- if $dotmp; then
- [[ "$file" != */* ]] && file="$TMP/$file"
- fi
- mkfiles_ret[i]=$file
- let i+=1
- done
-
- rm -f -- "${mkfiles_ret[@]}" || {
- # hopefully rm will have printed a more specific message.
- print -ru2 "Could not remove pre-existing files."
- return 1
- }
- for file in "${mkfiles_ret[@]}"; do
- # Use >> to avoid 0'ing the file in case a symlink was just made from
- # the filename to something we don't want to truncate
- >> "$file" || {
- print -ru2 "Could not create temp file $file"
- return 1
- }
- # These are very suspicious
- [ -s "$file" ] && {
- print -ru2 "New tempfile is not empty!!!"
- return 1
- }
- [ -O "$file" ] || {
- print -ru2 "Do not own $file!!!"
- return 1
- }
- done
- # Could do some more stuff here, but anyone concerned with security should
- # have chown authorization off for most users.
- return 0
- }
-
- # Usage: mktempfile name
- # Creates a tempfile name tempdir/#name$$, and sets the global mktempfile_ret
- # to that name. tempdir is a temp directory in $TMP, $TMPDIR, or /tmp, and $$
- # is the PID of the current process.
- # name should be 8 characters or less, so that the resulting filename will
- # not be more than 14 characters long (a limit on some machines).
- # Returns 0 on success, prints a diagnostic message & returns 1 on failure.
- function mktempfile {
- typeset file="#$1$$"
- mkfiles "$file" || return 1
- mktempfile_ret=${mkfiles_ret[0]}
- }
-
- typeset -i nList=0
- function CleanExit {
- [ nList -gt 0 ] && rm -f -- "${fLists[@]}"
- exit "$1"
- }
-
- # Do an operation on tape device.
- # Usage: TapeOp tape-cmd
- function TapeOp {
- typeset cDevs dev
-
- $debug && set -x
- if [ -n "$REMOTE" ]; then
- [ "$1" = rewind ] && print -rp "Rewinding..."
- for dev in ${Devs[*]}; do
- tail=${dev##*/}
- set -A cDevs -- "${cDevs[@]}" "${dev%/*}/x${tail#*r}"
- done
- rcmd "$RemoteSys" $UserArg "
- for ControlDev in ${cDevs[*]}
- do
- if [ -c \$ControlDev ]
- then
- tape $1 \$ControlDev
- exit 0
- fi
- done
- echo 'Could not find remote tape device for tape $1. Tried: ${cDevs[*]}'
- exit 1"
- else
- [ "$1" = rewind ] && print -rp "Rewinding $ControlDev ..."
- tape $1 "$ControlDev"
- fi
- }
-
- # Globals:
- # Uses filelist PrepList lname FINDOPTS PRESERVELISTS flist BLOCKSIZE REMOTE
- # RemoteSys UserArg rVerify SlowCmd PATH TapeSizeOpt Devs[]
- # Sets fLists[] nList NumGood GoodFS[]
- function Backup {
- typeset fs=$1
- typeset mountTail flist cpioCmd RCommand DDcmd dev
-
- if cd "$fs"; then
- if [[ "$filelist" = *{}* ]]; then
- [ "$fs" -ef / ] && mountTail=root || mountTail=${fs##*/}
- flist="${filelist%%{\}*}$mountTail${filelist#*{\}}"
- else
- flist=$filelist
- fi
- [[ "$flist" != /* ]] && flist="$fs/$flist"
- $PrepList || {
- mkfiles "$flist" || {
- print -ru2 "$lname: could not make temp file. Exiting."
- CleanExit 1
- }
- print -rp "Generating file list for $fs"
- find . -mount ! \( -name '#*' -o -name '#*' -o -name '*
- *' -o -name core \) $FINDOPTS -print > $flist
- [ "$PRESERVELISTS" = 1 ] || {
- fLists[nList]=$flist
- let nList+=1
- }
- }
- print -rp "Backing up $fs"
- cpioCmd="$CPIOPATH -aoC $((BLOCKSIZE*1024))"
- if [ -n "$REMOTE" ]; then
- RCommand="rcmd $RemoteSys $UserArg"
- $cpioCmd | if $rVerify; then
- if [ -z "$SlowCmd" ]; then
- $RCommand "$TCPIOPATH -it > /dev/null"
- else
- # Add to PATH to increase chances of picking up 'slow'.
- $RCommand \
- "PATH=\$PATH:/usr/local/bin; $SlowCmd |
- $TCPIOPATH -it > /dev/null"
- fi
- else
- if [ "${#Devs}" = 1 ]; then
- DDcmd="dd bs=${BLOCKSIZE}k of=${Devs[0]}"
- else
- DDcmd="
- for dev in ${Devs[*]}
- do
- if [ -c \$dev ]
- then
- dd bs=${BLOCKSIZE}k of=\$dev
- break
- fi
- done"
- fi
- if [ -z "$SlowCmd" ]; then
- $RCommand "$DDcmd"
- else
- $SlowCmd | $RCommand "$DDcmd"
- fi
- fi
- elif [ -z "$SlowCmd" ]; then
- $cpioCmd $TapeSizeOpt -O "$DEVICE"
- else
- $cpioCmd $TapeSizeOpt | $SlowCmd > "$DEVICE"
- fi < "$flist"
- $realControl && print -rp "$(TapeOp amount)"
- let NumGood+=1
- GoodFS[NumGood]=$fs
- else
- print -ru2 "Could not cd to $fs."
- fi
- }
-
- if [[ "$DEVICE" = *r+([!/]) ]]; then
- dir=${DEVICE%/*}
- tail=${DEVICE##*/}
- ControlDev="$dir/x${tail#*r}"
- $debug && print -ru2 "Generated control device name: $ControlDev"
- realControl=true
- else
- ControlDev=$DEVICE
- realControl=false
- fi
-
- if [ -z "$REMOTE" ]; then
- [ ! -c "$ControlDev" ] && ControlDev=$DEVICE
- $debug && print -ru2 "Final device name: $ControlDev"
- fi
-
- if $TestTape; then
- TapeOp status
- exit $?
- fi
-
- if [ -n "$NOLOG" ]; then
- unset logfile
- else
- if [ -z "$logfile" ]; then
- TMP=/tmp
- mktempfile fbackup. || {
- print -ru2 -- "$lname: exiting."
- exit 1
- }
- logfile=$mktempfile_ret
- else
- mkfiles -n -- "$logfile" || {
- print -ru2 -- "$lname: exiting."
- exit 1
- }
- logfile=${mkfiles_ret[0]}
- fi
- print -r -- "Writing status information to log file $logfile"
- fi
-
- # duplicate fd 1 as 3 for logger, because writing to fd 1 in coprocess
- # writes to the coprocess output pipe.
- exec 3>&1
-
- # Set up logger/timestamper as coprocess.
- # Use gawk if available to avoid repeatedly executing date.
- if type gawk > /dev/null; then
- gawk -v "logfile=$logfile" '
- {
- time = systime()
- if (time != otime) {
- date = strftime("%D %T",time)
- otime = time
- }
- line = date " " $0
- print line > "/dev/stdout"
- close("/dev/stdout") # flush output
- if (logfile != "") {
- print line >> logfile
- # flush to logfile to; it may be being remotely monitored
- close(logfile)
- }
- }' 1>&3 |&
- else
- (
- # SECONDS in subshell starts at 0; make sure oSec will initially be
- # different than it.
- typeset -i oSec=-1
- while read line; do
- # Only run date if the seconds have changed, to avoid executing
- # date many times for bursts of log activity
- if [ SECONDS -ne oSec ]; then
- date=$(date "+%D %T")
- oSec=SECONDS
- fi
- s="$date $line"
- print -r -u3 -- "$s"
- [ -n "$logfile" ] && print -r -- "$s" >> "$logfile"
- done
- ) |&
- fi
- Logger=$!
-
- if $CheckOnly; then
- set -A GoodFS -- "$@"
- else
- [ -z "$NOREWIND" ] && TapeOp rewind
- for fs; do
- Backup "$fs"
- done
- fi
-
- TapeOp rewind
-
- [ "$CHECK" = 1 ] || CleanExit 0
-
- if [ -n "$REMOTE" ]; then
- CheckCmd="
- for DEVICE in ${Devs[*]}
- do
- [ -c \$DEVICE ] && break
- done
- [ -c \$DEVICE ] || {
- echo 'Could not find remote tape device for tape check. Tried: ${Devs[*]}'
- exit 1
- }
- "
- fi
-
- CheckCmd="$CheckCmd
- set -- ${GoodFS[*]}
- num=${#GoodFS[*]}
- while [ \$# -gt 0 ]
- do "'
- extent=`expr $num - $# + 1`
- echo "Checking extent $extent ($1)" '"
- if $TCPIOPATH -it -C $((BLOCKSIZE*1024)) < "' $DEVICE > /dev/null
- then
- echo "Extent $extent ($1) OK"
- sleep 5
- else
- break
- fi
- shift
- done'
-
- if [ -n "$REMOTE" ]; then
- rcmd "$RemoteSys" $UserArg "$CheckCmd"
- else
- eval "$CheckCmd"
- fi 2>&1 | while read line; do print -rp -- "$line"; done
- #^^^ should be able to just do 1>&p, but it causes the logger to die (ksh bug?)
-
- TapeOp rewind
- CleanExit 0
-